package com.gitee.wsl.ext.base

import com.gitee.wsl.base.Relation
import kotlin.random.Random


// coercion
/**
 * Produces a range where min value is the minimum between [other.start] and [ClosedRange.start]
 * and max value is maximum between [other.endInclusive] and [ClosedRange.endInclusive]
 */
fun <T : Comparable<T>> ClosedRange<T>.expand(other: ClosedRange<T>): ClosedRange<T> =
    minOf(other.start, start)..maxOf(other.endInclusive, endInclusive)

/**
 * Produces a range that is fully contained within [other] range.
 */
infix fun <T : Comparable<T>> ClosedRange<T>.coerceIn(other: ClosedRange<T>): ClosedRange<T> =
    maxOf(start, other.start)..minOf(endInclusive, other.endInclusive)

/**
 * Expand the range left and right by [delta] and return the resulting range
 */
fun ClosedFloatingPointRange<Double>.expandBy(delta: Double): ClosedFloatingPointRange<Double> =
    start - delta..endInclusive + delta

/**
 * Shrink the range left and right by [delta] and return the resulting range
 */
fun ClosedFloatingPointRange<Double>.shrinkBy(delta: Double): ClosedFloatingPointRange<Double> = expandBy(-delta)

/**
 * Expand the range left and right by [delta] and return the resulting range
 */
fun ClosedFloatingPointRange<Int>.expandBy(delta: Int): IntRange =
    start - delta..endInclusive + delta

/**
 * Shrink the range left and right by [delta] and return the resulting range
 */
fun ClosedFloatingPointRange<Int>.shrinkBy(delta: Int): IntRange = expandBy(-delta)

/**
 * Expand the range left and right by [delta] and return the resulting range
 */
fun ClosedFloatingPointRange<Long>.expandBy(delta: Long): LongRange =
    start - delta..endInclusive + delta

/**
 * Shrink the range left and right by [delta] and return the resulting range
 */
fun ClosedFloatingPointRange<Long>.shrinkBy(delta: Long): LongRange = expandBy(-delta)

/**
 * Expand the range left and right by [delta] and return the resulting range
 */
fun ClosedFloatingPointRange<Float>.expandBy(delta: Float): ClosedFloatingPointRange<Float> =
    start - delta..endInclusive + delta

/**
 * Shrink the range left and right by [delta] and return the resulting range
 */
fun ClosedFloatingPointRange<Float>.shrinkBy(delta: Float): ClosedFloatingPointRange<Float> = expandBy(-delta)


/**
 * The exclusive end of this range.
 *
 * @throws IllegalStateException if [ClosedRange.endInclusive] is equal to [Int.MAX_VALUE]
 */
inline val ClosedRange<Int>.end: Int
    get() {
        check(endInclusive != Int.MAX_VALUE) { "The exclusive end of $this is greater than Int.MAX_VALUE." }
        return endInclusive + 1
    }


// random --------------------------------------------------------------------------------------------------------------

/**
 * Returns a random element from this range,
 * or throws a [NoSuchElementException] if this range is empty.
 */
@Suppress("NOTHING_TO_INLINE")
inline fun ClosedRange<Double>.random(): Double = random(Random)

/**
 * Returns a random element from this range using the specified source of randomness,
 * or throws a [NoSuchElementException] if this range is empty.
 */
fun ClosedRange<Double>.random(random: Random): Double =
    if (isEmpty()) throw NoSuchElementException("Cannot get random in empty range: $this")
    else random.nextDouble(start, endInclusive)

/**
 * Returns a random element from this range,
 * or `null` if this range is empty.
 */
@Suppress("NOTHING_TO_INLINE")
inline fun ClosedRange<Double>.randomOrNull(): Double? = randomOrNull(Random)

/**
 * Returns a random element from this range using the specified source of randomness,
 * or `null` if this range is empty.
 */
fun ClosedRange<Double>.randomOrNull(random: Random): Double? =
    if (isEmpty()) null else random.nextDouble(start, endInclusive)



// conversion

/**
 * Convert this range to a [ClosedFloatingPointRange] of [Float]s
 */
fun <T> ClosedRange<T>.toFloat(): ClosedFloatingPointRange<Float> where T : Number, T : Comparable<T> =
    start.toFloat()..endInclusive.toFloat()

/**
 * Convert this range to a [LongRange]
 */
fun <T> ClosedRange<T>.toLong(): LongRange where T : Number, T : Comparable<T> =
    start.toLong()..endInclusive.toLong()

/**
 * Convert this range to an [IntRange]
 */
fun <T> ClosedRange<T>.toInt(): IntRange where T : Number, T : Comparable<T> =
    start.toInt()..endInclusive.toInt()

/**
 * Convert this range to a [ClosedFloatingPointRange] of [Double]s
 */
fun <T> ClosedRange<T>.toDouble(): ClosedFloatingPointRange<Double> where T : Number, T : Comparable<T> =
    start.toDouble()..endInclusive.toDouble()

// operators

/**
 * Get [ClosedRange.start]
 */
operator fun <T : Comparable<T>> ClosedRange<T>.component1(): T = start

/**
 * Get [ClosedRange.endInclusive]
 */
operator fun <T : Comparable<T>> ClosedRange<T>.component2(): T = endInclusive

/**
 * Returns `true` if [this] range is **fully contained** within the [other] range
 */
infix operator fun <T : Comparable<T>> ClosedRange<T>.contains(other: ClosedRange<T>): Boolean =
    start <= other.start && endInclusive >= other.endInclusive

/**
 * Add [value] to **both start and end** of the range
 */
operator fun ClosedRange<Float>.plus(value: Float): ClosedFloatingPointRange<Float> =
    start + value..endInclusive + value

/**
 * Add [value] to **both start and end** of the range
 */
operator fun ClosedRange<Long>.plus(value: Long): LongRange = start + value..endInclusive + value

/**
 * Add [value] to **both start and end** of the range
 */
operator fun ClosedRange<Int>.plus(value: Int): IntRange = start + value..endInclusive + value

/**
 * Add [value] to **both start and end** of the range
 */
operator fun ClosedRange<Double>.plus(value: Double): ClosedFloatingPointRange<Double> =
    start + value..endInclusive + value

/**
 * Subtract [value] from **both start and end** of the range
 */
operator fun ClosedRange<Float>.minus(value: Float): ClosedFloatingPointRange<Float> = plus(-value)

/**
 * Subtract [value] from **both start and end** of the range
 */
operator fun ClosedRange<Long>.minus(value: Long): LongRange = plus(-value)

/**
 * Subtract [value] from **both start and end** of the range
 */
operator fun ClosedRange<Int>.minus(value: Int): IntRange = plus(-value)

/**
 * Subtract [value] from **both start and end** of the range
 */
operator fun ClosedRange<Double>.minus(value: Double): ClosedFloatingPointRange<Double> = plus(-value)


// asIterable ----------------------------------------------------------------------------------------------------------

/**
 * Creates an [Iterable] instance that wraps the original range returning
 * its elements when being iterated.
 *
 * Unless empty, the first element returned is [ClosedRange.start].
 * The remaining elements are computed by applying the specified [step] to
 * the most recently returned element.
 *
 * The iteration ends when the next element is no longer contained in this range.
 */
fun <T : Comparable<T>> ClosedRange<T>.asIterable(step: (T) -> T): Iterable<T> =
    Iterable {
        var next: T = start
        iterator {
            while (contains(next)) {
                yield(next)
                next = step(next)
            }
        }
    }


fun <T : Comparable<T>> ClosedFloatingPointRange<T>.isBoundOf(value: T) =
    value == start || value == endInclusive

internal val ClosedFloatingPointRange<Double>.length
    get() = endInclusive - start

fun ClosedFloatingPointRange<Double>.random() = start + Random.nextDouble() * length


fun <T : Comparable<T>> ClosedRange<T>.determineRelationship(other: ClosedRange<T>): Relation {
    if (start < other.start) {
        if (endInclusive < other.start) { return Relation.Before }
        if (endInclusive == other.start) { return Relation.Meets }
        if (endInclusive < other.endInclusive) { return Relation.Overlaps }
        if (endInclusive == other.endInclusive) { return Relation.IsFinishedBy }
        /* endInclusive > other.endInclusive */ return Relation.Contains
    } else if (start == other.start) {
        if (endInclusive < other.endInclusive) { return Relation.Starts }
        if (endInclusive == other.endInclusive) { return Relation.Equal }
        /* (endInclusive > other.endInclusive) */ return Relation.IsStartedBy
    } else /* start > other.start */ {
        if (start > other.endInclusive) { return Relation.After }
        if (start == other.endInclusive) { return Relation.IsMetBy }
        if (endInclusive < other.endInclusive) { return Relation.During }
        if (endInclusive == other.endInclusive) { return Relation.Finishes }
        /* endInclusive > other.endInclusive */ return Relation.IsOverlappedBy
    }
}